/*
 * @(#) $Header$
 *
 * Copyright (C) 2006  Daniel Léonard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package ca.forklabs.baselib.io;

import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.FilterOutputStream;
import java.io.OutputStream;
import ca.forklabs.baselib.util.Bits;

import java.io.IOException;

import static ca.forklabs.baselib.io.Streams.asDataOutputStream;

/**
 * Class {@code LittleEndianOutputStream} writes to its underlying stream
 * in a little-endian manner, least-significant byte first. This class is an
 * adaptation from the class in:
 * <blockquote>
 * Elliote Rusty Harold,
 * <i><a target="_blank" href="http://www.oreilly.com/catalog/javaio/">Java I/O</a></i> (p.31),
 * O'Reilly, March 1999, ISBN 1-56592-485-1.
 * </blockquote>
 *
 * @author   <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.baselib.io.LittleEndianOutputStream">Daniel Léonard</a>
 * @version $Revision$
 * @see   ca.forklabs.baselib.io.LittleEndianInputStream
 */
public class LittleEndianOutputStream extends FilterOutputStream implements DataOutput {

//---------------------------
// Instance variable
//---------------------------

   /** Buffer for the break methods of class {@link Bits}. */
   private byte[] pieces = new byte[8];

   /** Enclosed data output stream. */
   private DataOutputStream data_out;


//---------------------------
// Constructor
//---------------------------

   /**
    * Constructs a {@code LittleEndianOutputStream} build on top of the
    * underlying {@link OutputStream}.
    * @param   out   the output stream to write to.
    */
   public LittleEndianOutputStream(OutputStream out) {
      super(null);

      DataOutputStream dos = asDataOutputStream(out);
      this.setDataOut(dos);

      this.out = dos;
      }


//---------------------------
// Accessors and mutator
//---------------------------

   /**
    * Gets the byte array.
    * @return   the byte array.
    */
   protected byte[] getPieces() {
      return this.pieces;
      }

   /**
    * Gets the underlying data output stream.
    * @return   the underlying data output stream.
    */
   protected DataOutputStream getDataOut() {
      return this.data_out;
      }

   /**
    * Changes the underlying data output stream.
    * @param   dos   the new underlying data output stream.
    */
   protected void setDataOut(DataOutputStream dos) {
      this.data_out = dos;
      }


//---------------------------
// Implemented methods from java.io.DataOutput
//---------------------------

   /**
    * Gets the number of bytes written to this stream so far.
    * @return   the number of bytes written.
    */
   @SuppressWarnings("resource")
   public int size() {
      DataOutputStream dos = this.getDataOut();
      int size = dos.size();
      return size;
      }

   /**
    * Writes the {@code boolean} argument.
    * @param   z   the {@code boolean} value to be written.
    * @throws   IOException   if an I/O error occurs.
    */
   @Override
   @SuppressWarnings("resource")
   public void writeBoolean(boolean z) throws IOException {
      DataOutputStream dos = this.getDataOut();
      dos.writeBoolean(z);
      }

   /**
    * Writes the {@code byte} argument.
    * @param   b   the {@code byte} value to be written.
    * @throws   IOException   if an I/O error occurs.
    */
   @Override
   @SuppressWarnings("resource")
   public void writeByte(int b) throws IOException {
      DataOutputStream dos = this.getDataOut();
      dos.writeByte(b);
      }

   /**
    * Writes the {@code short} argument lowest byte first.
    * @param   s   the {@code short} value to be written.
    * @throws   IOException   if an I/O error occurs.
    */
   @Override
   public synchronized void writeShort(int s) throws IOException {
      byte[] b = this.getPieces();
      b = Bits.breakShort((short) s, b, 0);
      this.writeByte(b[1]);
      this.writeByte(b[0]);
      }

   /**
    * Writes the {@code char} argument lowest byte first.
    * @param   c   the {@code char} value to be written.
    * @throws   IOException   if an I/O error occurs.
    */
   @Override
   public void writeChar(int c) throws IOException {
      this.writeShort(c);
      }

   /**
    * Writes the {@code int} argument lowest byte first.
    * @param   i   the {@code int} value to be written.
    * @throws   IOException   if an I/O error occurs.
    */
   @Override
   public synchronized void writeInt(int i) throws IOException {
      byte[] b = this.getPieces();
      b = Bits.breakInt(i, b, 0);
      this.writeByte(b[3]);
      this.writeByte(b[2]);
      this.writeByte(b[1]);
      this.writeByte(b[0]);
      }

   /**
    * Writes the {@code long} argument lowest byte first.
    * @param   l   the {@code long} value to be written.
    * @throws   IOException   if an I/O error occurs.
    */
   @Override
   public synchronized void writeLong(long l) throws IOException {
      byte[] b = this.getPieces();
      b = Bits.breakLong(l, b, 0);
      this.writeByte(b[7]);
      this.writeByte(b[6]);
      this.writeByte(b[5]);
      this.writeByte(b[4]);
      this.writeByte(b[3]);
      this.writeByte(b[2]);
      this.writeByte(b[1]);
      this.writeByte(b[0]);
      }

   /**
    * Writes the {@code float} argument lowest byte first.
    * @param   f   the {@code float} value to be written.
    * @throws   IOException   if an I/O error occurs.
    */
   @Override
   public void writeFloat(float f) throws IOException {
      int i = Float.floatToRawIntBits(f);
      this.writeInt(i);
      }

   /**
    * Writes the {@code double} argument lowest byte first.
    * @param   d   the {@code double} value to be written.
    * @throws   IOException   if an I/O error occurs.
    */
   @Override
   public void writeDouble(double d) throws IOException {
      long l = Double.doubleToRawLongBits(d);
      this.writeLong(l);
      }

   /**
    * Writes out the string to the underlying output stream as a sequence of
    * {@code byte}s.
    * @param   s   the string to write.
    * @throws   IOException   if an I/O error occurs.
    */
   @Override
   @SuppressWarnings("resource")
   public void writeBytes(String s) throws IOException {
      DataOutputStream dos = this.getDataOut();
      dos.writeBytes(s);
      }

   /**
    * Writes out the string to the underlying output stream as a sequence of
    * {@code char}s.
    * @param   s   the string to write.
    * @throws   IOException   if an I/O error occurs.
    */
   @Override
   public void writeChars(String s) throws IOException {
      int len = s.length();
      for (int i = 0; i < len; i++) {
         char c = s.charAt(i);
         this.writeChar(c);
         }
      }

   /**
    * Writes a string to the underlying output stream using UTF-8 encoding in a
    * machine-independent manner.
    * @param   s   the string to write.
    * @throws   IOException   if an I/O error occurs.
    */
   @Override
   @SuppressWarnings("resource")
   public void writeUTF(String s) throws IOException {
      DataOutputStream dos = this.getDataOut();
      dos.writeUTF(s);
      }

   }
